package com.ruoyi.manager;

import com.ruoyi.common.constant.socket.MessageType;
import com.ruoyi.common.core.domain.entity.socket.bean.*;
import com.ruoyi.common.core.domain.entity.system.SysBoat;
import com.ruoyi.common.core.domain.entity.system.SysJishiOrder;
import com.ruoyi.common.core.domain.entity.system.SysTaocanOrder;
import com.ruoyi.common.core.domain.entity.system.UserBoatOrderInfo;
import com.ruoyi.common.enums.BoatStatus;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.handler.socket.SocketMessageHandler;
import com.ruoyi.handler.websocket.WebSocketMessageHandler;
import com.ruoyi.system.service.*;
import io.netty.channel.Channel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class SocketChannelManager {

    /**
     * 所有已连接的channel集合。
     */
    private static Set<Channel> socketChannelSet = new HashSet<>();
    /**
     * 船只信息SysBoat对象与Socket Channel的关联集合。
     */
    private static ConcurrentHashMap<Channel, SysBoat> socketChannelAndSysBoatMap = new ConcurrentHashMap<>();
    public static ConcurrentHashMap<Channel, ChannelInfo> channelAndChannelInfoMap = new ConcurrentHashMap<>();

    /**
     * 处理已失效的连接。
     *
     * @param channel
     */
    public static void handleSocketChannelInactive(Channel channel) {
        log.info("handleChannelInactive channel: {}", channel);

        removeChannel(channel);

        //船只socket连接断开后的处理方法。
        processAfterBoatChannelInactive(channel);
    }

    /**
     * 船只socket连接断开后的处理方法。
     *
     * @param channel
     */
    private static void processAfterBoatChannelInactive(Channel channel) {
        log.info("processAfterBoatChannelInactive channel: {}", channel);

        //根据当前的channel获取其对应的SysBoat对象
        SysBoat sysBoat = getSysBoatBySocketChannel(channel);
        if (Objects.nonNull(sysBoat)) {
            //从boatIdChannelMap中移除指定Channel。
            removeOnlineBoatChannel(channel, sysBoat);

            //解除当前channel与其channelInfo对象的绑定关系
            channelAndChannelInfoMap.remove(channel);

            Long boatId = sysBoat.getBoatId();
            //更新数据库中的船只状态为"离线"
            ISysBoatService sysBoatService = SpringUtils.getBean(ISysBoatService.class);
            sysBoatService.updateBoatStatusByBoadId(boatId, BoatStatus.Offline);

            Long scenicId = sysBoat.getScenicId();
            //需判断船只所属的景区id是否为空，因为如果没有在后台为船只分配所属景区的话则此处船只所属的景区id就会为空
            if (Objects.nonNull(scenicId)) {
                Channel scenicWebSocketChannel = WebSocketChannelManager.getScenicWebSocketChannelByScenicId(scenicId);
                //此处需判断scenicWebSocketChannel是否为空，因为当前船只所属的景区的管理员此时可能未登录监控后台，未登录的话scenicWebSocketChannel就会为空
                if (Objects.nonNull(scenicWebSocketChannel)) {
                    //向景区监控后台发送船只运营状态更新的消息
                    BoatOperationStatus boatOperationStatus = new BoatOperationStatus();
                    boatOperationStatus.setBoatId(boatId);
                    boatOperationStatus.setBoatIdentityId(sysBoat.getBoatIdentityId());
                    boatOperationStatus.setStatus(BoatStatus.Offline.getCode());
                    boatOperationStatus.setBoatCode(sysBoat.getBoatCode());

                    //给景区监控中心发送船只状态更新信息
                    log.info("向景区[scenicId={}]发送当前船只[boatCode={},boatIdentityId={}]离线信息！", scenicId, sysBoat.getBoatCode(), sysBoat.getBoatIdentityId());
                    WebSocketMessageHandler.sendMsgToScenicAdmin(scenicId, boatOperationStatus);
                } else {
                    log.debug("当前景区[scenicId={}]管理员不在线，向其发送船只离线通知失败！", scenicId);
                }
            }

            //查询当前船只此时是否有使用中的订单
            IUserBoatOrderInfoService userBoatOrderInfoService = SpringUtils.getBean(IUserBoatOrderInfoService.class);
            UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.getUserBoatOrderInfoOfUsingStatusByBoatIdentityId(sysBoat.getBoatIdentityId());
            if (Objects.nonNull(userBoatOrderInfo)) { //存在使用中的订单
                new Timer().schedule(new CheckBoatStatusTimerTask(userBoatOrderInfo), 60000);
            }


        } else {
            log.warn("当前channel对应的sysBoat对象为null.");
        }
    }

    static class CheckBoatStatusTimerTask extends TimerTask {
        private Channel channel;
        private UserBoatOrderInfo userBoatOrderInfo;

        public CheckBoatStatusTimerTask(UserBoatOrderInfo userBoatOrderInfo) {
            this.userBoatOrderInfo = userBoatOrderInfo;
        }

        @Override
        public void run() {
            String boatIdentityId = userBoatOrderInfo.getBoatIdentityId();
            ISysBoatService sysBoatService = SpringUtils.getBean(ISysBoatService.class);
            SysBoat sysBoat = sysBoatService.selectSysBoatByBoatIdentityId(boatIdentityId);
            if (Objects.nonNull(sysBoat)) {
                //船只状态。可能存在的值：0=离线,1=维护,2=空闲,3=支付倒计时中,4=拼团中,5=上船倒计时中,6=开锁等待中,7=手动驾驶中,8=自动驾驶中。
                //备注：一条新船只的默认状态为离线，每次上线后的初始状态为空闲。
                int boatStatus = sysBoat.getStatus();
                log.info("船只[boatIdentityId={}]断开连接已超过指定时长，现再次获取其当前状态[status={},statusDesc={}]！", boatIdentityId,
                        boatStatus, sysBoat.getStatusDesc());
                if (boatStatus != BoatStatus.ManualDriving.getCode() && boatStatus != BoatStatus.AutoDriving.getCode()) {
                    //只要船只当前不是手动驾驶或自动驾驶状态就都应该结束当前使用中的订单,如果是则不需要结束，继续保持即可
                    String orderId = userBoatOrderInfo.getOrderId();
                    int orderType = userBoatOrderInfo.getOrderType();
                    ISysOrderService sysOrderService = SpringUtils.getBean(ISysOrderService.class);
                    Date beginTime = null;
                    if (orderType == 0) {
                        ISysJishiOrderService sysJishiOrderService = SpringUtils.getBean(ISysJishiOrderService.class);
                        SysJishiOrder jishiOrder = sysJishiOrderService.selectSysJishiOrderByOrderId(orderId);
                        beginTime = jishiOrder.getBeginTime();
                    } else if (orderType == 1) {
                        ISysTaocanOrderService taocanOrderService = SpringUtils.getBean(ISysTaocanOrderService.class);
                        SysTaocanOrder taocanOrder = taocanOrderService.selectSysTaocanOrderByOrderId(orderId);
                        beginTime = taocanOrder.getBeginTime();
                    }

                    if (Objects.isNull(beginTime)) { //订单还未开始计费，还未开始计费的订单则直接关闭
                        sysOrderService.closeOrder(orderType, orderId);
                    } else { //订单已开始计费，已经开始计费的订单则停止计费即可
                        sysOrderService.stopOrder(orderType, orderId);
                    }
                }
            }
        }
    }

    @Data
    public static class ChannelInfo {
        /**
         * 解锁状态默认为0，及未解锁。
         */
        byte unlockStatus = 0;
        /**
         * 上锁状态默认为1，及已上锁。
         */
        byte lockStatus = 1;
        /**
         * 安全须知确认状态。
         */
        byte safetyInstructionConfirStatus;
        /**
         * 电子围栏报警状态。0：无报警，1：黄区报警，2：红区报警，3：无效值
         */
        byte fenceAlarmStatus;

        /**
         * 泡泡豚船只状态。0：无操作，1：空闲，2：手动，3：自动，4：离线，5：维护，6-14：保留，15：无效值
         */
        byte boatStatus;
        /**
         * 急停状态。可能值：0：无操作，1：急停，2：非急停，3：无效值，默认值：0。
         */
        byte urgencyStopStatus;
        /**
         * 电池电量百分比。，取值范围：0-100，即0%~100%。默认电池电量为100%。
         */
        byte batteryVolume = 100;
        /**
         * 电池电量报警是否已发出过，默认为false。
         */
        boolean isBatteryVolumeAlarmSend = false;
        /**
         * 电池温度。127表示0℃，示例：157表示电池温度为30℃，默认电池温度为0度。
         */
        int batteryTemperature = 0;
        /**
         * 电池温度报警是否已发出过，默认为false。
         */
        boolean isBatteryTemperatureAlarmSend = false;

        /**
         * 船只故障码集合。
         */
        private List<BoatErrorCodeInfo.BoatErrorCode> boatErrorCodeList;

        public ChannelInfo() {
            boatErrorCodeList = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                boatErrorCodeList.add(new BoatErrorCodeInfo.BoatErrorCode());
            }
        }
    }

    /**
     * 将当前已建立连接的channel添加到channel集合channelSet中，但此时还不知道该channel对应的船只身份与信息，所以此时还无法将channel与船只信息进行绑定，
     * 此时只是记录当前已连接的channel总数。
     *
     * @param channel
     */
    public static void addChannel(Channel channel) {
        socketChannelSet.add(channel);
        log.info("channel: {}已连接, 当前已连接的socket客户端数量：{}", channel.remoteAddress().toString(), socketChannelSet.size());

//        log.info("current thread: {}", Thread.currentThread()); //示例：Thread[nioEventLoopGroup-3-1,10,main]

        //模拟游客支付成功
//        new Timer().schedule(new TimerTask() {
//            @Override
//            public void run() {
////                testSendFenceInfo(channel);
////                testSendBoatRegisterInfo();
//                sendPayStatusInfoMessage(channel, new PayStatusInfo((byte) 1));
//            }
//        }, 35000);
    }


    /**
     * 发送消息给指定景区的当前所有在线船只。
     */
    public static void sendMsgToAllOnlineBoatOfAssignedScenic(Long scenicId, BoatMessage boatMessage) {
        log.info("sendMsgToAllOnlineBoat socketChannelAndSysBoatMap size= {}", SocketChannelManager.socketChannelAndSysBoatMap.keySet().size());
        for (Channel channel : socketChannelAndSysBoatMap.keySet()) {
            SysBoat sysBoat = socketChannelAndSysBoatMap.get(channel);
            if (Objects.equals(sysBoat.getScenicId(), scenicId)) {
                SocketMessageHandler.sendSocketMsg(channel, boatMessage);
            }
        }
    }

    /**
     * 发送消息给指定boatId的船只。
     *
     * @param boatId
     * @param boatMessage
     */
    public static void sendMsgToAssignedBoat(Long boatId, BoatMessage boatMessage) {
        Channel channel = getSocketChannelByBoatId(boatId);
        if (Objects.nonNull(channel)) {
            SocketMessageHandler.sendSocketMsg(channel, boatMessage);
        }
    }

    public static Map<Channel, SysBoat> getSocketChannelAndSysBoatMap() {
        return socketChannelAndSysBoatMap;
    }

    /**
     * 发送游客已支付成功(且也已扫码确认上船)的消息给船端软件。其目的是告知船端软件游客已可以通过船端屏幕滑动开锁并启动船只。
     *
     * @param channel
     * @param payStatusInfo
     */
    public static void sendPayStatusInfoMessage(Channel channel, PayStatusInfo payStatusInfo) {
        if (Objects.isNull(channel) || Objects.isNull(payStatusInfo)) return;
        BoatMessage boatMessage = new BoatMessage(MessageType.STATUS_MESSAGE, (byte) 1);
        List<BaseDataPiece> dataPieceList = boatMessage.getDataPieceList();
        dataPieceList.add(payStatusInfo);
        channel.writeAndFlush(boatMessage);
    }

    public static void testSendFenceInfo(Channel channel) {
        log.info("testSendFenceInfo execute.");
        BoatMessage boatMessage = new BoatMessage(MessageType.CONFIG_MESSAGE, (byte) 1);
        FenceInfo fenceInfo = new FenceInfo();
        fenceInfo.setCreateOrUpdateTime(new Date()); //date: Sat Jul 01 09:55:11 CST 2023
        fenceInfo.setGreenDistance(20);
        fenceInfo.setYellowDistance(20);
        List<Double> coordinates = fenceInfo.getCoordinates();
        coordinates.add(30.110110);
        coordinates.add(114.220220);
        coordinates.add(30.111111);
        coordinates.add(114.221221);
        coordinates.add(30.111111);
        coordinates.add(114.221221);
        coordinates.add(30.111155);
        coordinates.add(114.221266);
        coordinates.add(30.111133);
        coordinates.add(114.221244);

        List<BaseDataPiece> dataPieceList = boatMessage.getDataPieceList();
        dataPieceList.add(fenceInfo);

        SocketMessageHandler.sendSocketMsg(channel, boatMessage);
    }

    private static void testSendBoatRegisterInfo() {
        Channel firstChannel = SocketChannelManager.getFirstChannel();
        BoatMessage boatMessage = new BoatMessage(MessageType.CONFIG_MESSAGE, (byte) 1);
        List<BaseDataPiece> dataPieceList = boatMessage.getDataPieceList();
        BoatRegisterInfo boatRegisterInfo = new BoatRegisterInfo((byte) 1);
        boatRegisterInfo.setBoatIdentityId("abc0123456789xyz");
        dataPieceList.add(boatRegisterInfo);
        log.info("testSendBoatRegisterInfo boatMessage= {}", boatMessage);
        if (firstChannel != null) {
//            log.info("testSendBoatRegisterInfo firstChannel= {}", firstChannel);
            firstChannel.writeAndFlush(boatMessage);
        } else {
            log.warn("testSendBoatRegisterInfo firstChannel is null.");
        }
    }

    //测试用
    public static Channel getFirstChannel() {
        Object[] channelArr = socketChannelSet.toArray();
//        log.info("channelSet: {}", channelSet);
//        log.info("channelArr length= {}", channelArr.length);
        return channelArr.length > 0 ? (Channel) channelArr[0] : null;
    }

    /**
     * 通过船只的id(非出厂id)来查询其对应的与后端的socket连接通道。
     *
     * @param boatId
     * @return
     */
    public static Channel getSocketChannelByBoatId(Long boatId) {
        if (Objects.isNull(boatId)) return null;

        for (Channel channel : socketChannelAndSysBoatMap.keySet()) {
            if (boatId.equals(socketChannelAndSysBoatMap.get(channel).getBoatId())) {
                return channel;
            }
        }

        return null;
    }

    /**
     * 通过船只的出厂id来查询其对应的与后端的socket连接通道。
     *
     * @param boatIdentityId
     * @return
     */
    public static Channel getSocketChannelByBoatIdentityId(String boatIdentityId) {
        if (Objects.isNull(boatIdentityId)) return null;

        for (Channel channel : socketChannelAndSysBoatMap.keySet()) {
            if (boatIdentityId.equals(socketChannelAndSysBoatMap.get(channel).getBoatIdentityId())) {
                return channel;
            }
        }

        return null;
    }

    public static SysBoat getSysBoatBySocketChannel(Channel channel) {
        return socketChannelAndSysBoatMap.get(channel);
    }

    public static ChannelInfo getChannelInfoBySocketChannel(Channel channel) {
        return channelAndChannelInfoMap.get(channel);
    }

    public static void updateChannelInfo(Channel channel, ChannelInfo channelInfo) {
        ChannelInfo existChannelInfo = getChannelInfoBySocketChannel(channel);
        if (Objects.isNull(existChannelInfo)) return;
        channelAndChannelInfoMap.put(channel, channelInfo);
    }

    public static void removeChannel(Channel channel) {
        socketChannelSet.remove(channel);
        log.info("removeChannel: {}已断开连接，当前剩余已连接的socket客户端数量：{}", channel.remoteAddress().toString(), socketChannelSet.size());
    }

    public static void addOnlineBoatChannel(Channel channel, SysBoat sysBoat) {
        socketChannelAndSysBoatMap.put(channel, sysBoat);
        channelAndChannelInfoMap.put(channel, new ChannelInfo());
        log.info("船只[boatIdentityId={},boatCode={}]已连接，当前已连接的船只数量：{}", sysBoat.getBoatIdentityId(),
                sysBoat.getBoatCode(), socketChannelAndSysBoatMap.size());
    }

    /**
     * 更新在线船只的信息。
     *
     * @param sysBoat
     */
    public static void updateOnlineBoatInfo(SysBoat sysBoat) {
        Channel channel = getSocketChannelByBoatId(sysBoat.getBoatId());
        if (Objects.nonNull(channel)) { //当前船只确实在线
            socketChannelAndSysBoatMap.put(channel, sysBoat);
        }
    }

    /**
     * 从boatIdChannelMap中移除指定Channel。
     */
    public static void removeOnlineBoatChannel(Channel channel, SysBoat sysBoat) {
        socketChannelAndSysBoatMap.remove(channel);
        log.error("船只[boatIdentityId={},boatCode={}]已断开连接，当前剩余连接的船只数量：{}", sysBoat.getBoatIdentityId(),
                sysBoat.getBoatCode(), socketChannelAndSysBoatMap.size());
    }

    /**
     * 从boatIdChannelMap中移除所有Channel。此方法在服务端主动关闭socket服务时调用。
     */
    public static void clearAllChannel() {
        socketChannelSet.clear();
        socketChannelAndSysBoatMap.clear();
        channelAndChannelInfoMap.clear();
        log.warn("removeAllChannel: 清除所有channel!");
    }

    /**
     * 检查socket连接状态。
     */
    public static void checkConnectionStatus() {
        log.info("============================ 检查socket连接状态 ===============================");
        List<Channel> unconnectedSocketChannelList = new ArrayList<>();
        for (Channel channel : socketChannelSet) {
            if (!channel.isWritable()) {
                unconnectedSocketChannelList.add(channel);
            }
        }

        //清除socketChannelSet中所有未连接的channel
        for (Channel channel : unconnectedSocketChannelList) {
            socketChannelSet.remove(channel);
        }

        List<Channel> unconnectedBoatSocketChannelList = new ArrayList<>();
        for (Channel channel : socketChannelAndSysBoatMap.keySet()) {
            log.info("channel: {}, isActive: {}, isWritable: {}", channel, channel.isActive(), channel.isWritable());
            if (!channel.isWritable()) {
                unconnectedBoatSocketChannelList.add(channel);
                //船只socket连接断开后的处理方法。
                processAfterBoatChannelInactive(channel);
                SysBoat sysBoat = SocketChannelManager.getSysBoatBySocketChannel(channel);
                if (Objects.nonNull(sysBoat)) {
                    log.warn("检测到船端socket连接[channel={},boatCode={},boatIdentityId={}]异常，服务端主动清除该连接!", channel, sysBoat.getBoatCode(), sysBoat.getBoatIdentityId());
                }
            }
        }

        //清除socketChannelAndSysBoatMap中所有未连接的channel
        for (Channel channel : unconnectedBoatSocketChannelList) {
            socketChannelAndSysBoatMap.remove(channel);
        }
    }

    /**
     * 检查及打印当前socket连接情况。
     */
    public static void printCurrentSocketConnectionStatus() {
        //检查socket连接状态。
        checkConnectionStatus();

        log.info("当前socket连接总数：{}, 船端socket连接数：{}", socketChannelSet.size(), socketChannelAndSysBoatMap.size());
        log.info("船端socket连接情况如下：");
        SysBoat sysBoat;
        for (Channel socketChannel : socketChannelAndSysBoatMap.keySet()) {
            sysBoat = socketChannelAndSysBoatMap.get(socketChannel);
            log.info("{} =========> [boatId:{}, boatCode: {}, BoatIdentityId:{}]", socketChannel, sysBoat.getBoatId(), sysBoat.getBoatCode(), sysBoat.getBoatIdentityId());
        }


    }

}
